﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows.Media;

namespace ImageProcessing
{
    public delegate byte ByteConstOperation(byte pixel_byte, params double[] constants);

    public delegate byte[] PixelConstOperation(byte r_byte, byte g_byte, byte b_byte, params double[] constants);

    public delegate byte BytesOperation(byte a_pixel_byte, byte b_pixel_byte);

    public static class GlobalOperations
    {
        public static void ImagePixelAndConstantOperation(WriteableBitmap image, PixelConstOperation operation, params double[] constant)
        {
            byte[] pixels = new byte[image.PixelHeight * image.BackBufferStride];
            image.CopyPixels(pixels, image.BackBufferStride, 0);
            for (int i = 0; i < pixels.Length; i += 4)
            {
                byte[] result = operation(pixels[i], pixels[i + 1], pixels[i + 2], constant);
                pixels[i] = result[0];
                pixels[i + 1] = result[1];
                pixels[i + 2] = result[2];
            }
            image.WritePixels(new System.Windows.Int32Rect(0, 0, image.PixelWidth, image.PixelHeight), pixels, image.BackBufferStride, 0);
        }

        public static void ImageAndConstantOperation(WriteableBitmap image, ByteConstOperation operation, params double[] constant)
        {
            byte[] pixels = new byte[image.PixelHeight * image.BackBufferStride];
            image.CopyPixels(pixels, image.BackBufferStride, 0);
            for (int i = 0; i < pixels.Length; i++)
                if ((i + 1) % 4 != 0) pixels[i] = operation(pixels[i], constant);
            image.WritePixels(new System.Windows.Int32Rect(0, 0, image.PixelWidth, image.PixelHeight), pixels, image.BackBufferStride, 0);
        }

        public static WriteableBitmap ImageAndImageOperation(WriteableBitmap a_image, WriteableBitmap b_image, BytesOperation operation)
        {
            byte[] Apixels = new byte[a_image.PixelHeight * a_image.BackBufferStride];
            a_image.CopyPixels(Apixels, a_image.BackBufferStride, 0);
            byte[] Bpixels = new byte[b_image.PixelHeight * b_image.BackBufferStride];
            b_image.CopyPixels(Bpixels, b_image.BackBufferStride, 0);
            int stride = Math.Max(a_image.BackBufferStride, b_image.BackBufferStride);
            int height = Math.Max(a_image.PixelHeight, b_image.PixelHeight);
            byte[] pixels = new byte[height * stride];
            int shift = 0;
            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < stride; j++, shift++)
                {
                    if ((shift + 1) % 4 == 0)
                    {
                        pixels[shift] = 0xff;
                        continue;
                    }
                    if (a_image.PixelHeight <= i || b_image.PixelHeight <= i)
                    {
                        if (a_image.PixelHeight > b_image.PixelHeight && a_image.BackBufferStride <= j ||
                            a_image.PixelHeight < b_image.PixelHeight && b_image.BackBufferStride <= j)
                            pixels[shift] = 0;
                        else
                            pixels[shift] = a_image.PixelHeight > b_image.PixelHeight ? Apixels[i * a_image.BackBufferStride + j] : Bpixels[i * b_image.BackBufferStride + j];
                        continue;
                    }
                    if (a_image.BackBufferStride <= j || b_image.BackBufferStride <= j)
                    {
                        pixels[shift] = a_image.BackBufferStride > b_image.BackBufferStride ? Apixels[i * a_image.BackBufferStride + j] : Bpixels[i * b_image.BackBufferStride + j];
                        continue;
                    }
                    pixels[shift] = operation(Apixels[i * a_image.BackBufferStride + j], Bpixels[i * b_image.BackBufferStride + j]);
                }
            }
            var result = new WriteableBitmap(Math.Max(a_image.PixelWidth, b_image.PixelWidth), height, 96, 96, a_image.Format, a_image.Palette);
            result.WritePixels(new System.Windows.Int32Rect(0, 0, result.PixelWidth, result.PixelHeight), pixels, stride, 0);
            return result;
        }

        public static void InvertImageOperation(WriteableBitmap image)
        { GlobalOperations.ImageAndConstantOperation(image, new ByteConstOperation((pixel_byte, cons) => { return (byte)(Math.Abs(pixel_byte - cons[0])); }), 255); }

        public static void BinarizationByLevelImageOperation(WriteableBitmap image, byte level)
        { BinarizationByIntervalImageOperation(image, 0, level); }

        public static void BinarizationByIntervalImageOperation(WriteableBitmap image, byte interval_start, byte interval_end)
        {
            GlobalOperations.ImagePixelAndConstantOperation(image, new PixelConstOperation((a_byte, b_byte, c_byte, cons) =>
                {
                    byte brightValue = (byte)(0.3 * a_byte + 0.59 * b_byte + 0.11 * c_byte);
                    if (brightValue >= cons[0] && brightValue <= cons[1]) return new byte[] { 255, 255, 255 };
                    else return new byte[] { 0, 0, 0 };
                }), interval_start, interval_end);
        }

        public static double[] GetHistogram(WriteableBitmap image)
        {
            double[] result = new double[256];
            byte[] pixels = new byte[image.PixelHeight * image.BackBufferStride];
            image.CopyPixels(pixels, image.BackBufferStride, 0);
            long[] brightPixels = new long[256];
            for (int i = 0; i < pixels.Length; i += 4) brightPixels[(byte)Math.Round(0.3 * pixels[i] + 0.59 * pixels[i + 1] + 0.11 * pixels[i + 2])]++;
            long pixelsCount = image.PixelHeight * image.PixelWidth;
            double max = 0;
            for (int i = 0; i < 256; i++) { result[i] = (double)brightPixels[i] / pixelsCount; max = Math.Max(max, result[i]); }
            for (int i = 0; i < 256; i++) result[i] = result[i] / max;
            return result;
        }
    }
}
